//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.TridentComposer
{
    #region Using Directives
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Windows.Media;
    using System.Xml;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.DataLayer;
    #endregion Using Directives

    /// <summary>
    /// This class contains the logic related to fetch all the categories path from registry and library, 
    /// update categories into registry etc.
    /// </summary>
    public static class CategoriesComposer
    {
        /// <summary>
        /// Embedded categories xml path.
        /// </summary>
        private const string EmbeddedXmlCategories = "Embedded\\Xml\\Categories.xml";

        /// <summary>
        /// 
        /// </summary>
        private const string UserRootName = "UserWorkflowRoot";

        /// <summary>
        /// 
        /// </summary>
        private const string Category = "Category";

        /// <summary>
        /// 
        /// </summary>
        public const string SystemUserName = "System";

        /// <summary>
        /// constant string Windows Workflow.
        /// </summary>
        public const string BuiltInActivitiesName = "Windows Workflow";

        /// <summary>
        /// constant string WebServiceActivities.
        /// </summary>
        public const string WebServiceActivitiesName = "Web Service Activities";

        /// <summary>
        /// constant string ActivityRoot.
        /// </summary>
        public const string ActivityRootName = "Activity";

        /// <summary>
        /// 
        /// </summary>
        private const string UncategorizedName = "Uncategorized";

        /// <summary>
        /// constant string TridentWorkflow.
        /// </summary>
        private const string TridentWorkflowName = "Trident Workflow";

        /// <summary>
        /// constant string TridentActivities.
        /// </summary>
        private const string TridentActivitiesName = "Trident Activities";

        #region public static methods
        /// <summary>
        /// Read uncategorized workflows from registry.
        /// </summary>
        /// <param name="registryConnection">Registry Connection</param>
        /// <param name="userName">User Name.</param>
        public static Namespace GetUncategorizedWorkflowNamespace(Connection registryConnection, string userName)
        {
            #region Validations
            if (null == registryConnection)
            {
                throw new ArgumentNullException("registryConnection");
            }

            if (string.IsNullOrEmpty(userName))
            {
                throw new ArgumentNullException("userName");
            }
            #endregion

            Namespace result = null;
            try
            {
                // Retreive the users uncategorized workflows.
                User user = GetUser(userName, registryConnection);
                string myWorkflowText = TridentResourceManager.GetString("WorkFlowTreeHeader");
                result = FindCategoryByPath(user.WorkflowRoot, user.WorkflowRoot.Name + "/" + myWorkflowText);
            }
            catch (RecordNotFoundException)
            {
                return null;
            }
            return result;
        }

        /// <summary>
        /// Read uncategorized activities from registry.
        /// </summary>
        public static Namespace GetUncategorizedActivityNamespace(Connection connection)
        {
            Namespace result = null;
            #region validations
            if (connection == null)
            {
                throw new ArgumentNullException("connection");
            }
            #endregion
            try
            {
                User systemUser = GetSystemUser(connection);
                if (systemUser != null)
                {
                    Namespace activityRoot = systemUser.ActivityRoot;
                    if (activityRoot != null)
                    {
                        foreach (Namespace cat in activityRoot.Children)
                        {
                            result = FindCategoryByPath(cat, Namespace.UncategorizedName);
                            if (result != null)
                            {
                                break;
                            }
                        }
                    }
                }
            }
            catch (RecordNotFoundException)
            {
                return null;
            }
            return result;
        }

        /// <summary>
        /// Read all categories from registry and return list of category model object.
        /// </summary>
        /// <param workflowName="c">Registry connection.</param>
        /// <returns>List of category model object.</returns>
        public static Collection<ActivityCategoryModel> ReadActivityCategoriesFromRegistry(Connection connection)
        {
            #region validations
            if (connection == null)
            {
                throw new ArgumentNullException("connection");
            }

            #endregion

            Collection<ActivityCategoryModel> categories = new Collection<ActivityCategoryModel>();
            NamespaceCollection<Namespace> namespaces = null;

            // Get the system categories.
            User systemUser = GetSystemUser(connection);
            if (systemUser != null)
            {
                namespaces = CategoriesComposer.ReadActivityCategories(systemUser);
            }
            else
            {
                TridentErrorHandler.ThrowTridentCustomException(TridentErrorConstants.ErrorNumber4000032,
                    TridentResourceManager.GetString("SystemUserNotFound"));
            }


            // Create a activity category model for each category found.
            foreach (Namespace cat in namespaces)
            {
                ActivityCategoryModel catModel = CategoriesComposer.ConvertNamespacetoActivityCategory(cat);
                categories.Add(catModel);
            }

            return categories;
        }

        /// <summary>
        /// Read all categories from registry and return list of category model object.
        /// </summary>
        /// <param workflowName="c">Registry connection.</param>
        /// <returns>List of category model object.</returns>
        public static Collection<WorkflowCategoryModel> ReadWorkflowCategoriesFromRegistry(
            Connection connection,
            User user,
            bool showLabeledWorkflows)
        {
            #region validations
            if (connection == null)
            {
                throw new ArgumentNullException("connection");
            }

            if (user == null)
            {
                throw new ArgumentNullException("user");
            }
            #endregion

            Collection<WorkflowCategoryModel> categories = new Collection<WorkflowCategoryModel>();
            List<Namespace> namespaces = null;

            // Get the system categories.
            User systemUser = GetSystemUser(connection);

            if (!systemUser.Name.Equals(user.Name))
            {
                namespaces = CategoriesComposer.ReadWorkflowCategories(systemUser);

                // Create category models for each namespace present.
                foreach (Namespace cat in namespaces)
                {
                    WorkflowCategoryModel catModel = CategoriesComposer.ConvertNamespacetoWorkflowCategory(cat, showLabeledWorkflows);
                    categories.Add(catModel);
                }
            }

            namespaces = CategoriesComposer.ReadWorkflowCategories(user);

            // Create category models for each namespace present.
            foreach (Namespace cat in namespaces)
            {
                WorkflowCategoryModel catModel = CategoriesComposer.ConvertNamespacetoWorkflowCategory(cat, showLabeledWorkflows);
                categories.Add(catModel);
            }

            return categories;
        }

        /// <summary>
        /// Get all activities from a category.
        /// </summary>
        /// <param name="categoryId">The category ID.</param>
        /// <returns>A collection of activities.</returns>
        public static Collection<ActivityMetadata> GetActivitiesFromCategory(Guid categoryId, Connection connection)
        {
            try
            {
                Collection<ActivityMetadata> categoryActivities = new Collection<ActivityMetadata>();
                Namespace category = Namespace.Load(categoryId, connection);

                foreach (Activity activity in Activity.CommonSearches.GetAllActivities(connection, category, null))
                {
                    categoryActivities.Add(new ActivityMetadata(activity));
                }

                return categoryActivities;
            }
            catch (Exception exp)
            {
                TridentErrorHandler.ProcessKnownExceptionsInBusinessLogic(exp);
            }
            return null;
        }

        public static Collection<WorkflowCategoryModel> ReadSystemWorkflowCategories(
            Connection connection,
            string userName)
        {
            #region validations
            if (connection == null)
            {
                throw new ArgumentNullException("connection");
            }

            if (userName == null)
            {
                throw new ArgumentNullException("userName");
            }
            #endregion

            Collection<WorkflowCategoryModel> categories = new Collection<WorkflowCategoryModel>();
            List<Namespace> namespaces = null;

            // Get the system categories.
            User systemUser = GetSystemUser(connection);

            if (!systemUser.Name.Equals(userName))
            {
                namespaces = CategoriesComposer.ReadWorkflowCategories(systemUser);

                // Create category models for each namespace present.
                foreach (Namespace cat in namespaces)
                {
                    WorkflowCategoryModel catModel = CategoriesComposer.ConvertNamespacetoWorkflowCategory(cat, true);
                    categories.Add(catModel);
                }
            }

            return categories;
        }

        /// <summary>
        /// Read all categories from registry and return list of category the user can accesss.
        /// </summary>
        /// <param workflowName="c">Registry connection.</param>
        /// <returns>List of category model object.</returns>
        public static Collection<WorkflowCategoryModel> ReadWorkflowCategoriesForUser(
            Connection connection,
            User user)
        {
            #region validations
            if (connection == null)
            {
                throw new ArgumentNullException("connection");
            }

            if (user == null)
            {
                throw new ArgumentNullException("user");
            }
            #endregion

            Collection<WorkflowCategoryModel> categories = new Collection<WorkflowCategoryModel>();
            List<Namespace> namespaces = null;

            namespaces = CategoriesComposer.ReadWorkflowCategories(user);

            // Create category models for each namespace present.
            foreach (Namespace cat in namespaces)
            {
                WorkflowCategoryModel catModel = CategoriesComposer.ConvertNamespacetoWorkflowCategory(cat, true);
                categories.Add(catModel);
            }
            return categories;
        }

        /// <summary>
        /// Create a user and their namespaces if they dont exist.
        /// </summary>
        /// <param name="userName">The User Name.</param>
        /// <param name="regConnection">Registry connection.</param>
        public static void CreateUser(string userName, Connection regConnection)
        {
            try
            {
                User user = null;

                if (regConnection != null && regConnection.Alive)
                {
                    // Create a user if it doesnt exist.
                    user = User.Get(userName, regConnection);
                    user.Save();

                    // Create default workflow categories.
                    if (user.WorkflowRoot == null)
                    {
                        CreateUserWorkflowRoot(user, regConnection);
                    }
                }
            }
            catch (Exception exp)
            {
                TridentErrorHandler.ProcessKnownExceptionsInBusinessLogic(exp);
                TridentErrorHandler.HandleAndThrowBusinessLogicException(
                    exp,
                    TridentErrorConstants.ErrorNumber4000033,
                    TridentResourceManager.GetString("UserCreateFailed"));
            }
        }

        /// <summary>
        /// Two XML files define the workflow categories that are presented
        /// by the workflow designer application. The first file is stored
        /// internally to the project and can not be edited by the user. The
        /// second is stored in the application directory under the 'config'
        /// directory in a file called 'Categories.xml'.
        /// 
        /// The internal file is read first and creates a heirarch tree of
        /// workflow categories. Then the local file is checked, and if it
        /// exists and is well formatted, then it is merged into the categories
        /// created from the internal file.
        /// </summary>
        /// <returns></returns>
        public static Collection<ActivityCategoryModel> ReadAllCategories()
        {
            Collection<ActivityCategoryModel> result = new Collection<ActivityCategoryModel>();

            // Read from xml file
            XmlDocument userCategoryXml =
                XmlUtilities.GetLocalXml(CategoriesComposer.EmbeddedXmlCategories);

            if (userCategoryXml != null)
            {
                Collection<ActivityCategoryModel> userCats = CreateCategoriesFromXml(userCategoryXml);
                MergeCategories(result, userCats);
            }

            return result;
        }

        /// <summary>
        /// Validate whether the workflow name is unique in the category.
        /// </summary>
        /// <param name="categoryId">The category ID.</param>
        /// <param name="workflowName">The workflow name.</param>
        /// <returns>True if the name is unique.False otherwise.</returns>
        public static bool ValidateWorkflowName(Guid categoryId, string workflowName, Connection registryConnection)
        {
            #region Validations
            if (categoryId.Equals(Guid.Empty))
            {
                throw new ArgumentNullException("categoryId");
            }
            if (string.IsNullOrEmpty(workflowName))
            {
                throw new ArgumentNullException("workflowName");
            }
            if (registryConnection == null)
            {
                throw new ArgumentNullException("registryConnection");
            }
            #endregion
            bool result = true;
            try
            {
                Namespace category = Namespace.Load(categoryId, registryConnection);
                Collection<Activity> activities = category.GetWorkflows(workflowName);
                if (activities != null && activities.Count > 0)
                {
                    List<Activity> sortedActivities = activities.OrderByDescending(activity => activity.Version).ToList();
                    foreach (Activity activity in sortedActivities)
                    {
                        if (activity.IsLatestVersion())
                        {
                            result = false;
                            break;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.ProcessKnownExceptionsInBusinessLogic(ex);
            }
            return result;
        }


        /// <summary>
        /// Validate whether the workflow name is unique in the category.
        /// </summary>
        /// <param name="categoryId">The category ID.</param>
        /// <param name="workflowId">The workflow ID</param>
        /// <param name="workflowName">The workflow name.</param>
        /// <returns>True if the name is unique.False otherwise.</returns>
        public static bool ValidateWorkflowName(Guid categoryId, Guid workflowId, string workflowName, Connection registryConnection)
        {
            #region Validations
            if (categoryId.Equals(Guid.Empty))
            {
                throw new ArgumentNullException("categoryId");
            }
            if (workflowId.Equals(Guid.Empty))
            {
                throw new ArgumentNullException("workflowId");
            }
            if (string.IsNullOrEmpty(workflowName))
            {
                throw new ArgumentNullException("workflowName");
            }
            if (registryConnection == null)
            {
                throw new ArgumentNullException("registryConnection");
            }
            #endregion
            Namespace category = null;
            bool result = false;
            try
            {
                category = Namespace.Load(categoryId, registryConnection);
                Collection<Activity> activities = category.GetWorkflows(workflowName);

                if (activities != null && activities.Count > 0)
                {
                    List<Activity> sortedActivities = activities.OrderByDescending(activity => activity.Version).ToList();
                    if (!(sortedActivities[0] as IObject).ID.Equals(workflowId))
                    {
                        foreach (Activity activity in sortedActivities)
                        {
                            if ((activity as IObject).ID.Equals(workflowId))
                            {
                                result = true;
                                break;
                            }
                        }
                        if (result == false)
                        {
                            bool anotherLatestPresent = false;
                            foreach (Activity activity in sortedActivities)
                            {
                                if (activity.IsLatestVersion())
                                {
                                    anotherLatestPresent = true;
                                    break;
                                }
                            }
                            result = !anotherLatestPresent;
                        }
                    }
                    else
                    {
                        result = true;
                    }
                }
                else
                {
                    result = true;
                }
            }
            catch (RecordNotFoundException ex)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(
                    ex,
                    TridentErrorConstants.ErrorNumber4000089,
                    TridentResourceManager.GetString("CategoryNotFound"));
            }
            catch (Exception ex)
            {
                TridentErrorHandler.ProcessKnownExceptionsInBusinessLogic(ex);
            }
            return result;
        }

        /// <summary>
        /// Merges the categories found in the mergeFrom parameter to those found in the mergeTo parameter.
        /// This is done by checking the ID of each category. If that ID does not exist in the mergeTo
        /// categories, then the category is added to the mergeTo list. If a category with the same ID
        /// is found in the mergeTo list all ready then MergeCategories is recursively called on the
        /// two categories with matching IDs.
        /// 
        /// The category definition found in mergeTo always wins out in the case of matching IDs.
        /// </summary>
        private static void MergeCategories(Collection<ActivityCategoryModel> mergeTo, Collection<ActivityCategoryModel> mergeFrom)
        {
            MergeCategories(mergeTo, mergeFrom, null);
        }

        /// <summary>
        /// Given an XmlDocument that describes a category heirarchy, create
        /// the ActivityCategoryModel elements that make up that heirarchy.
        /// </summary>
        private static Collection<ActivityCategoryModel> CreateCategoriesFromXml(XmlDocument xml)
        {
            Collection<ActivityCategoryModel> result = new Collection<ActivityCategoryModel>();
            XmlElement catRoot = xml.DocumentElement;

            Collection<XmlElement> catElems =
                XmlUtilities.GetChildElementsByTagName(catRoot, CategoriesComposer.Category);
            foreach (XmlElement catElem in catElems)
            {
                ActivityCategoryModel category = CreateCategoryFromXml(catElem, null);
                if (category != null)
                {
                    result.Add(category);
                }
            }

            return result;
        }

        /// <summary>
        /// Creates the namespace in registry if not present.
        /// </summary>
        /// <param name="path">Category path.</param>
        /// <param name="registryConnection">Registry connection instance.</param>
        /// <param name="user">The user.</param>
        /// <returns>Namespace object.</returns>
        public static Namespace CreateNamespace(string path, Connection registryConnection, User user)
        {
            #region Validations
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException("path");
            }

            if (null == registryConnection)
            {
                throw new ArgumentNullException("registryConnection");
            }

            if (null == user)
            {
                throw new ArgumentNullException("user");
            }
            #endregion

            string[] categories = path.Split(new char[] { '/' });
            Namespace current = null;
            try
            {
                current = user.WorkflowRoot;
            }
            catch (RecordNotFoundException)
            {
            }
            if (current == null)
            {
                current = Namespace.Create(CategoriesComposer.UserRootName, registryConnection);

                current.IsEditable = false;

                // Save changes to registry.
                current.Save();
            }
            current.Refresh();

            bool colorSet = false;
            System.Drawing.Color currentColor = System.Drawing.Color.White;
            Random random = new Random();
            foreach (string category in categories)
            {
                Namespace match = null;
                try
                {
                    match = Namespace.Load(category, registryConnection);
                }
                catch (RecordNotFoundException)
                {
                }

                if (match == null || match.Parent.Name != current.Name)
                {
                    match = Namespace.Create(category, registryConnection);
                    if (!colorSet)
                    {
                        int red = random.Next(50, 255);
                        int green = random.Next(50, 255);
                        int blue = random.Next(50, 255);
                        currentColor = System.Drawing.Color.FromArgb(red, green, blue);
                        match.EditorColor = currentColor;
                    }
                    else
                    {
                        int red = currentColor.R - random.Next(10, 50);
                        red = red < 0 ? 0 : red;
                        int green = currentColor.G - random.Next(10, 50);
                        green = green < 0 ? 0 : green;
                        int blue = currentColor.B - random.Next(10, 50);
                        blue = blue < 0 ? 0 : blue;
                        currentColor = System.Drawing.Color.FromArgb(red, green, blue);
                        match.EditorColor = currentColor;
                    }
                    match.Parent = current;
                    match.IsEditable = false;

                    // Save changes to registry.
                    match.Save();
                }
                current = match;
                currentColor = match.EditorColor;
                colorSet = true;
            }

            current.Refresh();
            return current;
        }

        /// <summary>
        /// Gets the namespace from registry.
        /// </summary>
        /// <param name="path">Category path.</param>
        /// <param name="registryConnection">Registry connection instance.</param>
        /// <param name="createIfNotExists">This will be set true only from the installer to create new category</param>
        /// <returns>Namespace object.</returns>
        public static Namespace GetActivityNamespace(string path, Connection registryConnection, bool createIfNotExists)
        {
            #region Validations
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException("path");
            }

            if (null == registryConnection)
            {
                throw new ArgumentNullException("registryConnection");
            }

            #endregion

            Namespace current = null;
            User user = GetSystemUser(registryConnection);
            try
            {
                if (user != null)
                {
                    current = user.ActivityRoot;
                }
            }
            catch (RecordNotFoundException)
            {
            }
            if (current != null)
            {
                Namespace result = null;
                //Create the activity namespace, if called from installer
                if (createIfNotExists)
                {
                    if (!String.IsNullOrEmpty(path))
                    {
                        if (!path.IsNormalized())
                            path = path.Normalize();

                        // Recursive calling function.
                        string nextStep = (path.Contains('/') ? path.Substring(0, path.IndexOf('/')) : path);
                        string remains = (path.Contains('/') ? path.Substring(path.IndexOf('/') + 1) : String.Empty);
                        while (!String.IsNullOrEmpty(nextStep))
                        {
                            result = FindCategoryByPath(current.Children.ToArray<Namespace>(), nextStep);
                            if (null == result)
                            {
                                result = Namespace.Create(nextStep, registryConnection);
                                if (null != result)
                                {
                                    result.Parent = current;

                                    // Logic: If createIfNotExists to true, we assume the user who is creatign the 
                                    //             Namespace is SYSTEM and the Other useres will not able to edit /delete the Namespace.
                                    result.IsEditable = false;

                                    // Save changes to registry.
                                    result.Save();
                                }
                            }
                            current = result;
                            path = remains;
                            nextStep = (path.Contains('/') ? path.Substring(0, path.IndexOf('/')) : path);
                            remains = (path.Contains('/') ? path.Substring(path.IndexOf('/') + 1) : String.Empty);
                        }
                    }
                }
                else
                {
                    //Find the trident activities object
                    if (!String.IsNullOrEmpty(path))
                    {
                        if (!path.IsNormalized())
                        {
                            path = path.Normalize();
                        }
                        string nextStep = (path.Contains('/') ? path.Substring(0, path.IndexOf('/')) : path);

                        Collection<Namespace> availableNamespaces = Namespace.CommonSearches.GetNamespaceByName(nextStep, registryConnection);

                        if (availableNamespaces != null)
                        {
                            Guid parentID = (current as IObject).ID;
                            foreach (Namespace currentNamespace in availableNamespaces)
                            {
                                Guid currentID = (currentNamespace.Parent as IObject).ID;
                                if (currentID == parentID)
                                {
                                    result = FindCategoryByPath(currentNamespace, path);
                                    if (result != null)
                                    {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }

                return result;
            }
            return current;
        }

        /// <summary>
        /// Gets the workflow namespace from registry.
        /// </summary>
        /// <param name="path">Category path.</param>
        /// <param name="registryConnection">Registry connection instance.</param>
        /// <returns>Namespace object.</returns>
        public static Namespace GetWorkflowNamespaceById(Guid id, Connection registryConnection)
        {
            #region Validations
            if (id == Guid.Empty)
            {
                throw new ArgumentNullException("id");
            }

            if (null == registryConnection)
            {
                throw new ArgumentNullException("registryConnection");
            }
            #endregion

            Namespace result = null;
            try
            {
                result = Namespace.Load(id, registryConnection);
            }
            catch (RecordNotFoundException)
            {
                return null;
            }

            return result;
        }
        #endregion

        #region private static methods
        /// <summary>
        /// Two XML files define the workflow categories that are presented
        /// by the workflow designer application. The first file is stored
        /// internally to the project and can not be edited by the user. The
        /// second is stored in the application directory under the 'config'
        /// directory in a file called 'Categories.xml'.
        /// 
        /// The internal file is read first and creates a heirarch tree of
        /// workflow categories. Then the local file is checked, and if it
        /// exists and is well formatted, then it is merged into the categories
        /// created from the internal file.
        /// </summary>
        private static NamespaceCollection<Namespace> ReadActivityCategories(User currentUser)
        {
            Namespace activityRoot = currentUser.ActivityRoot;
            if (activityRoot == null)
            {
                TridentErrorHandler.ThrowTridentCustomException(TridentErrorConstants.ErrorNumber4000031,
                    TridentResourceManager.GetString("ActivityCategoryError"));
            }
            return activityRoot.Children;
        }

        /// <summary>
        /// Get all the workflow categories from the registry.
        /// </summary>
        /// <param name="c">The registry connection.</param>
        /// <param name="user">The user for whom the workflows need to be retreived.</param>
        /// <returns>The namespace collection.</returns>
        private static List<Namespace> ReadWorkflowCategories(User user)
        {
            List<Namespace> childrenCollection = new List<Namespace>();
            Namespace workflowRoot = user.WorkflowRoot;
            if (workflowRoot == null)
            {
                TridentErrorHandler.ThrowTridentCustomException(TridentErrorConstants.ErrorNumber4000031,
                    TridentResourceManager.GetString("WorkflowCategoryError"));
            }

            workflowRoot.Children.Refresh();
            foreach (Namespace nameSpace in workflowRoot.Children)
            {
                if (user.Name.Equals("System", StringComparison.OrdinalIgnoreCase) ||
                    (nameSpace.User != null && ((IObject)nameSpace.User).ID == ((IObject)user).ID))
                {
                    nameSpace.Activitys.Refresh();
                    childrenCollection.Add(nameSpace);
                }
            }
            return childrenCollection;
        }

        /// <summary>
        /// Create new my workflow namespace for user.
        /// </summary>
        /// <param name="user">User object.</param>
        /// <param name="conn">Registry Connection.</param>
        /// <returns>My workflow namespace object.</returns>
        private static void CreateUserWorkflowRoot(User user, Connection conn)
        {
            string myWorkflowText = TridentResourceManager.GetString("WorkFlowTreeHeader");
            //string uncategorizedText = TridentResourceManager..GetString("UncategorizedCategoryName");

            // Create a workflow root if it doesnt exist.
            if (user.WorkflowRoot == null)
            {
                Namespace userRoot = null;
                try
                {
                    userRoot = Namespace.Load(CategoriesComposer.UserRootName, conn);
                }
                catch (RecordNotFoundException)
                {

                }
                if (userRoot != null)
                {
                    userRoot = Namespace.Create(CategoriesComposer.UserRootName, conn);

                    userRoot.IsEditable = false;
                    userRoot.Save();
                }

                if (user.WorkflowRoot == null)
                {
                    user.WorkflowRoot = userRoot;
                    user.Save();
                }

                user.Refresh();

                // Create a namespace signifying the user's private workflows.
                Namespace myWorkflow = Namespace.Create(myWorkflowText, conn);
                myWorkflow.IsEditable = false;
                myWorkflow.Label = myWorkflowText;
                myWorkflow.User = user;
                myWorkflow.Parent = user.WorkflowRoot;

                // Save the changes.
                myWorkflow.Save();
            }
        }

        /// <summary>
        /// Convert Namespace object to ActivityCategoryModel object.
        /// </summary>
        /// <param name="cat">Namespace object.</param>
        /// <returns></returns>
        private static ActivityCategoryModel ConvertNamespacetoActivityCategory(Namespace cat)
        {
            ActivityCategoryModel catModel = new ActivityCategoryModel(((IObject)cat).ID.ToString());
            ActivityCategoryModel catModelChild;

            catModel.Name = cat.Name;

            catModel.Path = ((IObject)cat).ID.ToString();

            catModel.IsEditable = cat.IsEditable;

            catModel.Id = ((IObject)cat).ID;
            if (!String.IsNullOrEmpty(cat.Label))
            {
                catModel.Label = cat.Label;
            }
            else
            {
                catModel.Label = cat.Name;
            }

            if (cat.Children != null)
            {
                foreach (Namespace catChild in cat.Children)
                {
                    catModelChild = CategoriesComposer.ConvertNamespacetoActivityCategory(catChild);
                    catModelChild.Parent = catModel;
                    catModel.Categories.Add(catModelChild);
                }
            }

            return catModel;
        }

        /// <summary>
        /// Convert Namespace object to ActivityCategoryModel object.
        /// </summary>
        /// <param name="cat">Namespace object.</param>
        /// <returns></returns>
        private static WorkflowCategoryModel ConvertNamespacetoWorkflowCategory(Namespace category, bool showLabelledWorkflows)
        {
            if (category == null)
            {
                throw new ArgumentNullException("category");
            }

            // Create a new category model based on the namespace provided.
            WorkflowCategoryModel catModel = new WorkflowCategoryModel(category, showLabelledWorkflows);
            return catModel;
        }

        /// <summary>
        /// Create a ActivityCategoryModel from a 'Category' XmlElement
        /// Works recursively to build up children CategoryModels as well
        /// </summary>
        /// <param name="elem"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        private static ActivityCategoryModel CreateCategoryFromXml(XmlElement elem, ActivityCategoryModel parent)
        {
            ActivityCategoryModel result = null;

            if (elem != null)
            {

                // Read the values associated with the element category
                string id = elem.GetAttribute("ID");

                if (String.IsNullOrEmpty(id))
                {
                    throw new ArgumentException("Found a category with no ID specified");
                }

                result = new ActivityCategoryModel(id);

                result.Parent = parent;
                result.Name = elem.GetAttribute("Name");
                result.Label = elem.GetAttribute("Label");
                result.Description = elem.GetAttribute("Description");

                string colorName = elem.GetAttribute("EditorColor");

                if (!String.IsNullOrEmpty(colorName))
                {
                    result.PrimaryEditorColor = (Color)ColorConverter.ConvertFromString(colorName);
                }

                string imageSource = elem.GetAttribute("Icon");

                if (!String.IsNullOrEmpty(imageSource))
                {
                    //System.Windows.Controls.Image img = new System.Windows.Controls.Image();
                    ImageSourceConverter converter = new ImageSourceConverter();
                    result.IconImageSource = (ImageSource)converter.ConvertFromString(imageSource);
                }

                // Recursively read in the child categories
                Collection<XmlElement> catElems =
                    XmlUtilities.GetChildElementsByTagName(elem, "Category");

                foreach (XmlElement catElem in catElems)
                {
                    ActivityCategoryModel child = CreateCategoryFromXml(catElem, result);
                    if (child != null)
                    {
                        result.Categories.Add(child);
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Look through a list of Namespaces and their children to find the Namespace
        /// described by the path. If no Namespace is found, null is returned.
        /// 
        /// For example, given the path "First/Second" this method will look for a Namespace
        /// with ID "First" amongst the categories parameter, then look for a child Namespace
        /// with the ID "Second" amongst the first's children Namespaces.
        /// </summary>
        private static Namespace FindCategoryByPath(Namespace[] categories, string path)
        {
            Namespace result = null;

            foreach (Namespace cat in categories)
            {
                result = FindCategoryByPath(cat, path);

                if (result != null)
                {
                    break;
                }
            }

            return result;
        }

        /// <summary>
        /// Look through a list of Namespaces and their children to find the Namespace
        /// described by the path. If no Namespace is found, null is returned.
        /// </summary>
        /// <param name="category"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        private static Namespace FindCategoryByPath(Namespace category, string path)
        {
            Namespace result = null;

            if (!String.IsNullOrEmpty(path))
            {
                if (!path.IsNormalized())
                    path = path.Normalize();

                // Recursive calling function.
                string nextStep = (path.Contains('/') ? path.Substring(0, path.IndexOf('/')) : path);
                string remains = (path.Contains('/') ? path.Substring(path.IndexOf('/') + 1) : String.Empty);

                if (String.Equals(category.Name, nextStep, StringComparison.OrdinalIgnoreCase))
                {
                    if (remains.Length > 0)
                        result = FindCategoryByPath(category.Children.ToArray<Namespace>(), remains);
                    else
                        result = category;
                }
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="mergeTo"></param>
        /// <param name="mergeFrom"></param>
        /// <param name="parent"></param>
        private static void MergeCategories(Collection<ActivityCategoryModel> mergeTo, Collection<ActivityCategoryModel> mergeFrom, ActivityCategoryModel parent)
        {
            foreach (ActivityCategoryModel fromCat in mergeFrom)
            {
                bool foundMatch = false;

                foreach (ActivityCategoryModel toCat in mergeTo)
                {
                    if (toCat.Name.Equals(fromCat.Name))
                    {
                        foundMatch = true;
                        MergeCategories(toCat.Categories, fromCat.Categories, toCat);
                        break;
                    }
                }

                if (!foundMatch)
                {
                    mergeTo.Add(fromCat);
                    fromCat.Parent = parent;
                }
            }
        }

        /// <summary>
        /// Get the system user object.
        /// </summary>
        /// <param name="regConn">The registry connection.</param>
        /// <returns>The system user.</returns>
        private static User GetSystemUser(Connection regConn)
        {
            User systemUser = null;
            try
            {
                systemUser = User.Load(CategoriesComposer.SystemUserName, regConn);
            }
            catch (RecordNotFoundException exp)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(
                    exp,
                    TridentErrorConstants.ErrorNumber4000032,
                    TridentResourceManager.GetString("SystemUserNotFound"));
            }
            catch (Exception exp)
            {
                TridentErrorHandler.ProcessKnownExceptionsInBusinessLogic(exp);
            }
            return systemUser;
        }

        /// <summary>
        /// Get the required user object.
        /// </summary>
        /// <param name="regConn">The registry connection.</param>
        /// <returns>The required user.</returns>
        private static User GetUser(string userName, Connection regConn)
        {
            User user = null;
            try
            {
                user = User.Load(userName, regConn);
            }
            catch (RecordNotFoundException exp)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(
                    exp,
                    TridentErrorConstants.ErrorNumber4000032,
                    TridentResourceManager.GetString("UserNotFound"));
            }
            catch (Exception exp)
            {
                TridentErrorHandler.ProcessKnownExceptionsInBusinessLogic(exp);
            }
            return user;
        }

        /// <summary>
        /// Create system user's categories.
        /// </summary>
        /// <param name="registryConnection">Registry Connection.</param>
        public static void CreateSystemUserCategories(Connection registryConnection)
        {
            #region Validations
            if (null == registryConnection)
            {
                throw new ArgumentNullException("registryConnection");
            }
            #endregion

            User user = null;
            Namespace tridentWorkflowCategory = null;
            Namespace uncategorizedCategory = null;
            Namespace webServiceActCategory = null;

            try
            {
                user = User.Get(CategoriesComposer.SystemUserName, registryConnection);

                if (user.ActivityRoot == null)
                {
                    user.ActivityRoot = Namespace.Create(CategoriesComposer.ActivityRootName, registryConnection);
                    user.ActivityRoot.IsEditable = false;
                    user.ActivityRoot.Save();
                    user.Save();

                    uncategorizedCategory = Namespace.Create(CategoriesComposer.UncategorizedName, registryConnection);
                    uncategorizedCategory.Parent = user.ActivityRoot;
                    uncategorizedCategory.IsEditable = false;
                    uncategorizedCategory.Save();

                    webServiceActCategory = Namespace.Create(CategoriesComposer.WebServiceActivitiesName, registryConnection);
                    webServiceActCategory.Parent = user.ActivityRoot;
                    webServiceActCategory.IsEditable = false;
                    webServiceActCategory.Save();
                }

                if (user.WorkflowRoot == null)
                {
                    user.WorkflowRoot = Namespace.Create(Namespace.WorkflowRootName, registryConnection);
                    user.WorkflowRoot.IsEditable = false;
                    user.WorkflowRoot.Save();
                    user.Save();

                    tridentWorkflowCategory = Namespace.Create(CategoriesComposer.TridentWorkflowName, registryConnection);
                    tridentWorkflowCategory.Parent = user.WorkflowRoot;
                    tridentWorkflowCategory.IsEditable = false;
                    tridentWorkflowCategory.Save();
                }
            }
            catch (Exception exp)
            {
                TridentErrorHandler.ProcessKnownExceptionsInBusinessLogic(exp);
                TridentErrorHandler.HandleAndThrowBusinessLogicException(
                    exp,
                    TridentErrorConstants.ErrorNumber4000032,
                    exp.Message);
            }
            finally
            {
                user = null;
                tridentWorkflowCategory = null;
                uncategorizedCategory = null;
            }
        }
        #endregion
    }
}
